package core;

import java.io.*;
import java.lang.reflect.Array;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * author:木易阿发
 * time：220707
 * moditime：220710
 *
 * port：8888
 * 1、启动服务器:构造方法 new ServerSocek对象并绑定程序端口
 * 2、等待连接：
 * 3、获取客户端发送的信息及客户端主机信息并打印在控制台
 * 4、多线程支持被多个客户端连接互不干扰
 * 5、广播给所有客户端其他客户端发送过来的消息
 * 6、将PrintWriter数组换成集合列表优化减少代码和拓展的便捷性
 */
public class Servers {
    int serverNum ;
    private ServerSocket  serverSocket;
//    private PrintWriter[] pws={};//在根类创建一个输出流数组用来存放所有客户端发送过来的消息
    private Collection<PrintWriter> pws = new ArrayList<>();//在根类创建一个输出流数组用来存放所有客户端发送过来的消息
    public static void main(String[] args) {
        Servers server = new Servers();//new对象同时初始化
        //调用等待连接方法
        server.startServer();
    }

    // 1、构造器初始化启动服务器指定端口
    public Servers(){
        try {
            System.out.println("duang 瓮....");
            serverSocket = new ServerSocket(8889);
            System.out.println("服务器已成功运行.哔..哔...哔....");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 2、等待客户端连接
    public void startServer(){

        //4、多线程支持被多个客户端连接互不干扰
        try {
            while (true) {//循环不断接受用户连接从而启动新线程
                // 该线程任务是负责与指定的客户端进行交互
                Socket socket = serverSocket.accept();
//                System.out.println("ID-" + (++serverNum) + "-" + socket.getInetAddress().getHostName() + "(成功接入)");
                //没得到一个连接就启动一个线程单独交互
                Runnable clientHandler = new ClientHandler(socket, ++serverNum);//线程任务类
                Thread thread = new Thread(clientHandler);//线程对象
                thread.start();//启动线程
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    //3、获取客户端发送的信息及客户端主机信息并打印在控制台
    //客户端处理程序：处理服务器发回来的消息等
   private class ClientHandler implements Runnable{
        private int serverNum;//用户编号
        private Socket socket;
        private String host;//记录访问对象的主机地址信息
        public ClientHandler(Socket socket,int serverNum) {
            this.serverNum=serverNum;
            this.socket=socket;
            host=socket.getInetAddress().getHostName();
        }
        @Override
        public void run() {//交互任务
            PrintWriter pw = null;//输出流
            try {
                InputStream is = socket.getInputStream();;//通过socket获取输入流，得到客户端发送过来的消息
                InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);//字符转换流，将is的字节流转换为字符
                BufferedReader br = new BufferedReader(isr);//缓冲字节输入流，块读取

                //通过start方法循环传输过来的socket获取输出流给每个客户端广播消息
                OutputStream os = socket.getOutputStream();//通过socket获取字节输出流
                OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);//字节转换流，将字符转换成字节写出指定编码
                BufferedWriter bw = new BufferedWriter(osw);//缓冲输出流，保证语句的完整性
                pw = new PrintWriter(bw,true);//字符输出流（特别注意开启行刷新），按行写出控制台输入的消息
                //将输出流存入输出流数组
                synchronized (Servers.this) {//线程同步锁，避免数组出现线程安全
//                    pws = Arrays.copyOf(pws, pws.length + 1);//先对数组扩容
//                    pws[pws.length - 1] = pw;//把输出流对象存入数组的最后一个空间位置
                    pws.add(pw);
                }
                sendMessage("ID"+(serverNum)+"-IP"+host+" 接入上线，当前在线人数："+pws.size());//上线广播
                //向所有客户端广播消息
                String message;
                while ((message = br.readLine()) != null) {
                    sendMessage("ID"+(serverNum)+"-IP"+host+"-"+message);//向每个客户端发送当前客户端发送的消息
                }
            } catch (IOException e) {
                //当客户端强行关闭，服务端这里readLine会抛出异常，这里就可以catch异常从而执行finally
            }finally {
                synchronized (Servers.this) {//线程锁
                    //如果当前pw被关闭就将当前pw从广播数组中删除并通知下线
//                    for (int i = 0; i < pws.length; i++) {
//                        if (pws[i] == pw) {//找到数组中的当前pw
//                            pws[i] = pws[pws.length - 1];//将数组中的最后一个元素赋值到被关闭的pw所在数组的位置
//                            pws = Arrays.copyOf(pws, pws.length - 1);//将数组缩容
//                            //通知下线
//                            sendMessage("ID"+serverNum+"-IP"+host+"-关闭下线了，当前在线人数："+pws.length);
//                            break;
//                        }
//                    }
                    pws.remove(pw);
                    sendMessage("ID"+serverNum+"-IP"+host+"-关闭下线了，当前在线人数："+pws.size());
                }
            }
        }

         //5、广播给所有客户端其他客户端发送过来的消息
        //将当前客户端发的消息发送给所有客户端
        private void sendMessage(String line){
            System.out.println(line);
            synchronized (Servers.this) {//线程锁
//                for (int i = 0; i < pws.length; i++) {
//                    pws[i].println(line);//调用每个客户端socket对象进行消息发送
//                }
                for (PrintWriter pw:pws
                     ) {
                    pw.println(line);
                }
            }
        }
    }
}
